/*
 * author	: calvin
 * email	: calvinwilliams@163.com
 *
 * Licensed under the LGPL v2.1, see the file LICENSE in base directory.
 */

#include "cdbc.h"

static char	_cdbc_sg_db_type_name[][10+1] = { "" , "MySQL" , "PostgreSQL" , "Sqlite" , "Oracle" } ;
static char	_cdbc_sg_db_lowercase_type_name[][10+1] = { "" , "mysql" , "postgresql" , "sqlite" , "oracle" } ;

static TLS int	_cdbc_sg_last_errno = 0 ;
static TLS int	_cdbc_sg_last_native_errno = 0 ;
static TLS char	_cdbc_sg_last_native_error[ 256 ] = "" ;
static TLS char	_cdbc_sg_last_sqlstate[ 6 ] = "" ;

#define CDBC_DATABASE_TYPE_MYSQL_ID		1
#define CDBC_DATABASE_TYPE_POSTGRESQL_ID	2
#define CDBC_DATABASE_TYPE_ORACLE_ID		4
#define CDBC_DATABASE_TYPE_SQLITE_ID		3

struct DatabaseDriver
{
	char				*driver_name ;
	
	void				*so_handler ;
	
	funcInitDriverLibrary		*pfuncInitDriverLibrary ;
	funcEndDriverLibrary		*pfuncEndDriverLibrary ;
	funcConnectToDatabase		*pfuncConnectToDatabase ;
	funcDisconnectFromDatabase	*pfuncDisconnectFromDatabase ;
	funcExecuteSql			*pfuncExecuteSql ;
	funcAutoCommitTransaction	*pfuncAutoCommitTransaction ;
	funcBeginTransaction		*pfuncBeginTransaction ;
	funcCommitTransaction		*pfuncCommitTransaction ;
	funcRollbackTransaction		*pfuncRollbackTransaction ;
} ;

static TLS struct DatabaseDriverManager
{
	int			dummy ;
	struct DatabaseDriver	db_driver[ sizeof(_cdbc_sg_db_type_name)/sizeof(_cdbc_sg_db_type_name[0]) ] ;
} _cdbc_g_dbdrivermanager = { 0 } ;

struct DatabaseDriver *DBCGetDatabaseDriver( char *db_type_name )
{
	int			db_id ;
	struct DatabaseDriver	*p_db_driver = NULL ;
	char			pathfilename[ PATH_MAX ] ;
	int			nret = 0 ;
	
	_cdbc_sg_last_errno = 0 ;
	
	for( db_id = 1 ; db_id < sizeof(_cdbc_sg_db_type_name)/sizeof(_cdbc_sg_db_type_name[0]) ; db_id++ )
	{
		if( strcasecmp( db_type_name , _cdbc_sg_db_type_name[db_id] ) == 0 )
		{
			p_db_driver = _cdbc_g_dbdrivermanager.db_driver + db_id ;
			break;
		}
	}
	if( db_id >= sizeof(_cdbc_sg_db_type_name)/sizeof(_cdbc_sg_db_type_name[0]) )
		return NULL;
	
	if( p_db_driver->so_handler )
		return p_db_driver;
	
	p_db_driver->driver_name = _cdbc_sg_db_type_name[db_id] ;
	
	do
	{
		memset( pathfilename , 0x00 , sizeof(pathfilename) );
		snprintf( pathfilename , sizeof(pathfilename)-1 , "libdbdriver_%s.so" , _cdbc_sg_db_lowercase_type_name[db_id] );
		p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
		if( p_db_driver->so_handler )
			break;
		
		memset( pathfilename , 0x00 , sizeof(pathfilename) );
		snprintf( pathfilename , sizeof(pathfilename)-1 , "%s/lib/libdbdriver_%s.so" , getenv("HOME") , _cdbc_sg_db_lowercase_type_name[db_id] );
		p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
		if( p_db_driver->so_handler )
			break;
		
		memset( pathfilename , 0x00 , sizeof(pathfilename) );
		snprintf( pathfilename , sizeof(pathfilename)-1 , "/usr/lib/libdbdriver_%s.so" , _cdbc_sg_db_lowercase_type_name[db_id] );
		p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
		if( p_db_driver->so_handler )
			break;
		
		memset( pathfilename , 0x00 , sizeof(pathfilename) );
		snprintf( pathfilename , sizeof(pathfilename)-1 , "/lib/libdbdriver_%s.so" , _cdbc_sg_db_lowercase_type_name[db_id] );
		p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
		if( p_db_driver->so_handler )
			break;
		
	}
	while(0);
	if( p_db_driver->so_handler == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_FILE_NOT_FOUND ;
		return NULL;
	}
	
	p_db_driver->pfuncInitDriverLibrary = (funcInitDriverLibrary *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_INITDRIVERLIBRARY ) ;
	if( p_db_driver->pfuncInitDriverLibrary )
	{
		nret = p_db_driver->pfuncInitDriverLibrary() ;
		if( nret )
		{
			_cdbc_sg_last_errno = CDBC_ERROR_INIT_FAILED_IN_DRIVER ;
			dlclose( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
			return NULL;
		}
	}
	
	p_db_driver->pfuncEndDriverLibrary = (funcEndDriverLibrary *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_ENDDRIVERLIBRARY ) ;
	
	p_db_driver->pfuncConnectToDatabase = (funcConnectToDatabase *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_CONNECTTODATABASE ) ;
	if( p_db_driver->pfuncConnectToDatabase == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		dlclose( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
		return NULL;
	}
	
	p_db_driver->pfuncDisconnectFromDatabase = (funcDisconnectFromDatabase *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_DISCONNECTFROMDATABASE ) ;
	if( p_db_driver->pfuncDisconnectFromDatabase == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		dlclose( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
		return NULL;
	}
	
	p_db_driver->pfuncExecuteSql = (funcExecuteSql *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_EXECUTESQL ) ;
	if( p_db_driver->pfuncExecuteSql == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		dlclose( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
		return NULL;
	}
	
	p_db_driver->pfuncAutoCommitTransaction = (funcAutoCommitTransaction *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_AUTOCOMMITTRANSACTION ) ;
	p_db_driver->pfuncBeginTransaction = (funcBeginTransaction *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_BEGINTRANSACTION ) ;
	p_db_driver->pfuncCommitTransaction = (funcCommitTransaction *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_COMMITTRANSACTION ) ;
	p_db_driver->pfuncRollbackTransaction = (funcRollbackTransaction *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_ROLLBACKTRANSACTION ) ;
	
	return p_db_driver;
}

char *DBCGetDatabaseDriverName( struct DatabaseDriver *db_driver )
{
	return db_driver->driver_name;
}

void DBCUnloadAllDatabaseDrivers()
{
	int			db_id ;
	struct DatabaseDriver	*p_db_driver = NULL ;
	
	_cdbc_sg_last_errno = 0 ;
	
	for( db_id = 1 ; db_id < sizeof(_cdbc_sg_db_type_name)/sizeof(_cdbc_sg_db_type_name[0]) ; db_id++ )
	{
		p_db_driver = _cdbc_g_dbdrivermanager.db_driver + db_id ;
		if( p_db_driver->so_handler && p_db_driver->pfuncEndDriverLibrary )
		{
			p_db_driver->pfuncEndDriverLibrary();
		}
	}
	
	return;
}

struct DatabaseConnection *DBCConnectToDatabase( struct DatabaseDriver *db_driver , char *db_host , int db_port , char *db_user , char *db_pass , char *db_name )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return NULL;
	}
	
	if( db_driver->pfuncConnectToDatabase == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		return NULL;
	}
	
	return db_driver->pfuncConnectToDatabase( db_host , db_port , db_user , db_pass , db_name );
}

void DBCDisconnecFromDatabase( struct DatabaseDriver *db_driver , struct DatabaseConnection **db_conn )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncDisconnectFromDatabase == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		return;
	}
	
	db_driver->pfuncDisconnectFromDatabase( db_conn );
	
	return;
}

void DBCExecuteSql( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn , char *sql , struct FieldBind *binds_array , int binds_array_length , int *row_count , int *col_count , struct FieldInfo **query_field_set , char ***query_result_set , int *affected_count )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncExecuteSql == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		return;
	}
	
	db_driver->pfuncExecuteSql( db_conn , sql , binds_array , binds_array_length , row_count , col_count , query_field_set , query_result_set , affected_count );
	
	return;
}

void DBCFreeSqlResult( struct FieldInfo **query_field_set , char ***query_result_set )
{
	int			col_no ;
	int			col_count = 0 ;
	int			row_count = 0 ;
	struct FieldInfo	*p_field_info = NULL ;
	char			**p = NULL ;
	int			result_no ;
	int			result_count ;
	
	_cdbc_sg_last_errno = 0 ;
	
	if( query_field_set && (*query_field_set) )
	{
		for( col_no = 0 , p_field_info = (*query_field_set) , col_count = 0 ; p_field_info->field_name ; col_no++ , p_field_info++ , col_count++ )
		{
			if( p_field_info->field_name == NULL )
				break;
			
			free( p_field_info->field_name );
		}
		
		row_count = p_field_info->field_length ;
		
		free( (*query_field_set) ); (*query_field_set) = NULL ;
	}
	
	if( query_result_set && (*query_result_set) && col_count > 0 && row_count > 0 )
	{
		result_count = row_count * col_count ;
		for( result_no = 0 , p = (*query_result_set) ; result_no < result_count ; result_no++ , p++ )
		{
			if( (*p) )
				free( (*p) );
		}
		
		free( (*query_result_set) ); (*query_result_set) = NULL ;
	}
	
	return;
}

void DBCAutoCommitTransaction( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn , unsigned char enable_autocommit )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL || db_driver->pfuncAutoCommitTransaction == NULL )
		return;
	
	db_driver->pfuncAutoCommitTransaction( db_conn , (enable_autocommit?1:0) );
	
	return;
}

void DBCBeginTransaction( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncBeginTransaction == NULL )
		return;
	
	db_driver->pfuncBeginTransaction( db_conn );
	
	return;
}

void DBCCommitTransaction( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncCommitTransaction == NULL )
		return;
	
	db_driver->pfuncCommitTransaction( db_conn );
	
	return;
}

void DBCRollbackTransaction( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncRollbackTransaction == NULL )
		return;
	
	db_driver->pfuncRollbackTransaction( db_conn );
	
	return;
}

void DBCSetLastErrno( int last_errno )
{
	_cdbc_sg_last_errno = last_errno ;
	
	return;
}

int DBCGetLastErrno()
{
	return _cdbc_sg_last_errno;
}

void DBCSetLastNativeErrno( long last_native_errno )
{
	_cdbc_sg_last_native_errno = last_native_errno ;
	
	return;
}

long DBCGetLastNativeErrno()
{
	return _cdbc_sg_last_native_errno;
}

void DBCSetLastNativeError( char *error_str )
{
	memset( _cdbc_sg_last_native_error , 0x00 , sizeof(_cdbc_sg_last_native_error) );
	strncpy( _cdbc_sg_last_native_error , error_str , sizeof(_cdbc_sg_last_native_error)-1 );
	
	return;
}

char *DBCGetLastNativeError()
{
	return _cdbc_sg_last_native_error;
}

void DBCSetLastSqlState( char *sqlstate )
{
	memset( _cdbc_sg_last_sqlstate , 0x00 , sizeof(_cdbc_sg_last_sqlstate) );
	strncpy( _cdbc_sg_last_sqlstate , sqlstate , sizeof(_cdbc_sg_last_sqlstate)-1 );
	
	return;
}

char *DBCGetLastSqlState()
{
	return _cdbc_sg_last_sqlstate;
}

int InitResizableBuffer( struct ResizableBuffer *rb , size_t init_size , size_t increase_size )
{
	memset( rb , 0x00 , sizeof(struct ResizableBuffer) );
	
	rb->buf_base = (char*)malloc( init_size ) ;
	if( rb->buf_base == NULL )
	{
		free( rb );
		return -2;
	}
	memset( rb->buf_base , 0x00 , init_size );
	rb->buf_ptr = rb->buf_base ;
	rb->data_length = 0 ;
	rb->buf_size = init_size ;
	
	rb->increase_size = increase_size ;
	
	return 0;
}

void CleanResizableBuffer( struct ResizableBuffer *rb )
{
	if( rb )
	{
		if( rb->buf_base )
		{
			free( rb->buf_base ); rb->buf_base = NULL ;
		}
	}
	
	return;
}

char *AppendResizableBuffer( struct ResizableBuffer *rb , char *data , uint32_t data_len , char **rebase )
{
	char		*buf_ptr = NULL ;
	
	if( rb->buf_base == NULL )
		return NULL;
	
	if( rb->data_length + data_len + 1 > rb->buf_size-1 )
	{
		uint32_t	new_buf_size ;
		char		*new_buf_base = NULL ;
		
		if( rb->data_length + data_len + 1 <= rb->buf_size-1 + rb->increase_size )
			new_buf_size = rb->buf_size + rb->increase_size ;
		else
			new_buf_size = rb->data_length + data_len + 1 + 1 ;
		
		new_buf_base = (char*)realloc( rb->buf_base , new_buf_size ) ;
		if( new_buf_base == NULL )
			return NULL;
		rb->buf_ptr = new_buf_base + (rb->buf_ptr-rb->buf_base) ;
		rb->buf_base = new_buf_base ;
		memset( rb->buf_ptr , 0x00 , new_buf_size-rb->buf_size );
		rb->buf_size = new_buf_size ;
		
		if( rebase )
			(*rebase) = rb->buf_base ;
	}
	
	buf_ptr = rb->buf_ptr ;
	
	memcpy( rb->buf_ptr , data , data_len );
	rb->buf_ptr[data_len] = '\0' ;
	rb->data_length += data_len + 1 ;
	rb->buf_ptr += data_len + 1 ;
	
	return buf_ptr;
}

